package com.myz.springboot2.xml.utils;

import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLFilterImpl;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.sax.SAXSource;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;

public class JaxbUtil {

    /**
     * 默认编码UTF-8
     */
    private static final String ENCODING = "utf-8";

    /**
     * 对象转XML（默认编码UTF-8，格式化输出）
     *
     * @param o 实体
     * @return
     */
    public static String toXml(Object o) {
        StringWriter writer = null;
        try {
            writer = new StringWriter();
            toXml(o, writer, ENCODING, true, false);
            return writer.toString();
        } finally {
            try {
                writer.close();
            } catch (IOException e) {
            }
        }
    }

    /**
     * 对象转XML并保存到文件（默认编码UTF-8，格式化输出）
     *
     * @param o    实体
     * @param file 目标文件
     * @return
     */
    public static boolean toXml(Object o, File file) {
        BufferedWriter writer = null;
        try {
            writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), ENCODING));
            toXml(o, writer, ENCODING, true, false);
            return true;
        } catch (IOException e) {
            return false;
        } finally {
            try {
                writer.close();
            } catch (IOException e) {
            }
        }
    }

    /**
     * 对象转XML并保存到文件（默认编码UTF-8）
     *
     * @param o        实体
     * @param file     目标文件
     * @param isFormat 是否格式化
     * @return
     */
    public static boolean toXml(Object o, File file, boolean isFormat) {
        BufferedWriter writer = null;
        try {
            writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), ENCODING));
            toXml(o, writer, ENCODING, isFormat, false);
            return true;
        } catch (IOException e) {
            return false;
        } finally {
            try {
                writer.close();
            } catch (IOException e) {
            }
        }
    }

    /**
     * 对象转XML
     *
     * @param o               对象
     * @param writer          写入字符流
     * @param encoding        编码
     * @param format          是否格式化
     * @param ignoreNullField 是否忽略null值字段
     * @return
     */
    public static void toXml(Object o, Writer writer, String encoding, boolean format, boolean ignoreNullField) {
        try {
            JAXBContext context = JAXBContext.newInstance(o.getClass());
            Marshaller marshaller = context.createMarshaller();
            if (!ignoreNullField) {
                marshaller.setListener(new NullFieldListener());
            }
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, format);
            marshaller.setProperty(Marshaller.JAXB_ENCODING, encoding);
            marshaller.setProperty(Marshaller.JAXB_FRAGMENT, false);
            marshaller.marshal(o, writer);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * XML转bean
     *
     * @param xml XML字符串
     * @param c   转换目标类型
     * @return
     */
    public static <T> T toBean(String xml, Class<T> c) {
        return toBean(new StringReader(xml), c);
    }

    /**
     * XML转bean（默认UTF-8）
     *
     * @param xml XML文件
     * @param c   转换目标类型
     * @return
     */
    public static <T> T toBean(File xml, Class<T> c) {
        return toBean(xml, c, ENCODING);

    }

    /**
     * XML转bean
     *
     * @param xml    XML文件
     * @param c      转换目标类型
     * @param encode 以何种编码读取文件（默认UTF-8）
     * @return
     */
    public static <T> T toBean(File xml, Class<T> c, String encode) {
        try {
            encode = (encode == null || StrUtil.isEmpty(encode.trim())) ? ENCODING : encode;
            BufferedReader reader = new BufferedReader(new InputStreamReader(Files.newInputStream(xml.toPath()), encode));
            return toBean(reader, c);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * XML转bean
     *
     * @param reader 读取字符流
     * @param c      转换目标类型
     */
    @SuppressWarnings("unchecked")
    public static <T> T toBean(Reader reader, Class<T> c) {
        T t = null;
        try {
            JAXBContext context = JAXBContext.newInstance(c);
            Unmarshaller unmarshaller = context.createUnmarshaller();
            XMLReader xmlReader = createSecureXmlReader();
            InputSource inputSource = new InputSource(reader);
            SAXSource saxSource = new SAXSource(xmlReader, inputSource);
            t = (T) unmarshaller.unmarshal(saxSource);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return t;
    }

    /**
     * Null值的属性显示空节点
     */
    static class NullFieldListener extends Marshaller.Listener {

        static final String BLANK_CHAR = "";

        @Override
        public void beforeMarshal(Object source) {
            super.beforeMarshal(source);
            Field[] fields = ReflectUtil.getFields(source.getClass());
            for (Field f : fields) {
                f.setAccessible(true);
                try {
                    if (f.getType() == String.class && f.get(source) == null) {
                        f.set(source, BLANK_CHAR);
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * JAXB命名空间处理
     */
    static class PdeXmlFilterImpl extends XMLFilterImpl {

        private String rootNamespace = null;

        private boolean isRootElement = true;

        @Override
        public void startDocument() throws SAXException {
            super.startDocument();
        }

        @Override
        public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
            if (this.isRootElement) {
                this.isRootElement = false;
                qName = "文件说明";
                localName += " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema\" xmlns=\"" + uri + "\"";
                System.out.println(localName);
            }
            super.startElement(uri, localName, qName, atts);
        }

        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            super.endElement(uri, localName, qName);
        }

        @Override
        public void startPrefixMapping(String prefix, String uri) throws SAXException {
            if (this.rootNamespace != null) {
                uri = this.rootNamespace;
            }
            super.startPrefixMapping(prefix, uri);
        }
    }


    private static XMLReader createSecureXmlReader() throws JAXBException {
        try {
            SAXParserFactory spf = SAXParserFactory.newInstance();
            spf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
            spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
            spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
            spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
            return spf.newSAXParser().getXMLReader();
        } catch (Exception e) {
            throw new JAXBException("Failed to create XMLReader", e);
        }
    }
}
